package cn.elead.chaos.framework.web.endpoint;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodIntrospector;
import org.springframework.util.ClassUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import cn.hutool.core.util.ObjectUtil;

public class EpomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

	private Map<String, EpomEndPoint> handlers;

	@Override
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		if (ObjectUtil.isNotNull(handlers)) {
			handlers.entrySet().stream().map(Entry::getValue).collect(Collectors.toList())
					.forEach(a -> epomDetectHandlerMethods(a));
		}
	}

	public EpomRequestMappingHandlerMapping(Map<String, EpomEndPoint> handlers) {
		this.handlers = handlers;
	}

	/**
	 * Look for handler methods in the specified handler bean.
	 * 
	 * @param handler either a bean name or an actual handler instance
	 * @see #getMappingForMethod
	 */
	private void epomDetectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler)
				: handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> {
						try {
							return getMappingForMethod(method, userType);
						} catch (Throwable ex) {
							throw new IllegalStateException(
									"Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
						}
					});
			if (logger.isTraceEnabled()) {
				logger.trace(epomFormatMappings(userType, methods));
			}

			Map<RequestMappingInfo, HandlerMethod> handlerMethods = getHandlerMethods();
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				// 判断是否已经存在
				if(!handlerMethods.containsKey(mapping)) {
					registerHandlerMethod(handler, invocableMethod, mapping);
				}
			});
		}
	}

	private String epomFormatMappings(Class<?> userType, Map<Method, RequestMappingInfo> methods) {
		String formattedType = Arrays.stream(ClassUtils.getPackageName(userType).split("\\."))
				.map(p -> p.substring(0, 1)).collect(Collectors.joining(".", "", "." + userType.getSimpleName()));
		Function<Method, String> methodFormatter = method -> Arrays.stream(method.getParameterTypes())
				.map(Class::getSimpleName).collect(Collectors.joining(",", "(", ")"));
		return methods.entrySet().stream().map(e -> {
			Method method = e.getKey();
			return e.getValue() + ": " + method.getName() + methodFormatter.apply(method);
		}).collect(Collectors.joining("\n\t", "\n\t" + formattedType + ":" + "\n\t", ""));
	}
}
